using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using TestCases.CompareAssert.Core;
using TestCases.CompareAssert.Core.Comparers.Adapters;
using TestCases.CompareAssert.Core.Comparers.Interfaces;
using TestCases.CompareAssert.Core.Comparers.Members;
using TestCases.CompareAssert.Core.Helpers;
using TestCases.CompareAssert.Core.Interfaces;
using TestCases.CompareAssert.Matchers;
using TestCases.CompareAssert.Settings.Interfaces;
using TestCases.Utils.Pairs;

namespace TestCases.CompareAssert.Settings
{    
    public class CompareSetup: ICompareSetup, ICompareSettings
    {

        private readonly IList<IPair<IObjectMatcher<ICompareContext>, CompareSetup>> _children = 
            new List<IPair<IObjectMatcher<ICompareContext>, CompareSetup>>();

        private CompareSetup _currentSettings;

        private IResultSettings _result;
        private IObjectComparer _comparer;

        private readonly IndexesMatcher _ignoredElements = new IndexesMatcher();
        private readonly ObjectMatcher<object> _ignoredEntries = new ObjectMatcher<object>();
        private readonly StringMatcher _ignoredProperties = new StringMatcher();
        private readonly StringMatcher _ignoredFields = new StringMatcher();
        private readonly StringMatcher _disabledComparers = new StringMatcher();
        private readonly StringMatcher _disabledGenericsComparers = new StringMatcher();
        private AccessFlags _propertiesFlags = AccessFlags.Default;
        private AccessFlags _fieldsFlags = AccessFlags.Default;
        private IEqualityComparer<object> _keysComparer;
        private bool _ignoreReadOnlyProperties;
        private bool _forceEqualsComparer;
        private bool _forceHashCodeComparer;

        public CompareSetup()
        {
            _currentSettings = this;
        }


        #region ICompareSetup members
        public ICompareSetup Reset()
        {
            _result = null;
            _comparer = null;
            _ignoredProperties.Reset();
            _ignoredElements.Reset();
            _ignoredFields.Reset();
            _ignoredEntries.Reset();
            _disabledComparers.Reset();
            _disabledGenericsComparers.Reset();
            _ignoreReadOnlyProperties = false;
            _keysComparer = null;
            _propertiesFlags = AccessFlags.Default;
            _fieldsFlags = AccessFlags.Default;
            return this;
        }
        
        public void FullReset()
        {
            _children.Clear();
            Reset();
        }

        public ICompareSetup CompareBy(IObjectComparer comparer)
        {
            _comparer = comparer;
            return this;
        }

        public ICompareSetup CompareBy(IComparer comparer)
        {
            return CompareBy(new ComparerAdapter(comparer));
        }

        public ICompareSetup CompareBy(IEqualityComparer comparer)
        {
            return CompareBy(new EqualityComparerAdapter(comparer));
        }

        public ICompareSetup CompareBy<T>(IEqualityComparer<T> comparer)
        {
            return CompareBy(new EqualityComparerAdapter<T>(comparer));
        }

        public ICompareSetup CompareBy<T>(IComparer<T> comparer)
        {
            return CompareBy(new ComparerAdapter<T>(comparer));
        }

        public ICompareSetup CompareBy(CompareDelegate func)
        {
            return CompareBy(new DelegateComparer(func));
        }

        public ICompareSetup CompareBy<T>(CompareDelegate<T> func)
        {
            return CompareBy(new DelegateComparer<T>(func));
        }

        public ICompareSetup ResultIs(CompareStatus status)
        {
            return ResultIs(new ResultSettings(status));
        }

        public ICompareSetup ResultIs(CompareStatus status, string message)
        {
            return ResultIs(new ResultSettings(status, message));
        }

        public ICompareSetup ResultIs(CompareStatus status, string message, object args)
        {
            return ResultIs(new ResultSettings(status, message, args));
        }

        public ICompareSetup ResultIs(bool value)
        {
            return ResultIs(new ResultSettings(value));
        }

        public ICompareSetup ResultIs(bool value, string message)
        {
            return ResultIs(new ResultSettings(value, message));            
        }

        public ICompareSetup ResultIs(bool value, string message, object args)
        {
            return ResultIs(new ResultSettings(value, message, args));            
        }

        public ICompareSetup ResultIs(IResultSettings result)
        {
            _result = result;
            return this;
        }

        public ICompareSetup DisableComparer(string name)
        {
            _disabledComparers.AddValue(name);
            return this;
        }

        public ICompareSetup DisableComparers(IEnumerable<string> comparers)
        {
            _disabledComparers.AddValues(comparers);
            return this;
        }

        public ICompareSetup DisableComparersBy(string regex)
        {
            _disabledComparers.AddRegex(new Regex(regex));
            return this;
        }

        public ICompareSetup DisableComparersBy(Predicate<string> predicate)
        {
            _disabledComparers.AddPredicate(predicate);
            return this;
        }

        public ICompareSetup IgnoreProperty(string property)
        {
            _ignoredProperties.AddValue(property);
            return this;
        }

        public ICompareSetup IgnoreProperties(IEnumerable<string> properties)
        {
            _ignoredProperties.AddValues(properties);
            return this;
        }

        public ICompareSetup IgnorePropertiesBy(string regex)
        {
            _ignoredProperties.AddRegex(new Regex(regex));
            return this;
        }

        public ICompareSetup IgnorePropertiesBy(Predicate<string> predicate)
        {
            _ignoredProperties.AddPredicate(predicate);
            return this;
        }

        public ICompareSetup IgnoreField(string field)
        {
            _ignoredFields.AddValue(field);
            return this;
        }

        public ICompareSetup IgnoreFields(IEnumerable<string> fields)
        {
            _ignoredFields.AddValues(fields);
            return this;
        }

        public ICompareSetup IgnoreFieldsBy(string regex)
        {
            _ignoredFields.AddRegex(new Regex(regex));
            return this;
        }

        public ICompareSetup IgnoreFieldsBy(Predicate<string> predicate)
        {
            _ignoredFields.AddPredicate(predicate);
            return this;
        }

        public ICompareSetup IgnoreElementAt(int dimension, long index)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimension), new NumberMatcher<long>(index));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(int dimension, long minIndex, long maxIndex)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimension), new NumberMatcher<long>(minIndex, maxIndex));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(int dimension, IEnumerable<long> indexes)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimension), new NumberMatcher<long>(indexes));
            return this;
        }

        public ICompareSetup IgnoreElementsBy(int dimension, Predicate<long> predicate)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimension), new NumberMatcher<long>(predicate));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(int minDimension, int maxDimension, long index)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(minDimension, maxDimension), new NumberMatcher<long>(index));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(int minDimension, int maxDimension, long minIndex, long maxIndex)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(minDimension, maxDimension), new NumberMatcher<long>(minIndex, maxIndex));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(int minDimension, int maxDimension, IEnumerable<long> indexes)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(minDimension, maxDimension), new NumberMatcher<long>(indexes));
            return this;
        }

        public ICompareSetup IgnoreElementsBy(int minDimension, int maxDimension, Predicate<long> predicate)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(minDimension, maxDimension), new NumberMatcher<long>(predicate));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(IEnumerable<int> dimensions, long index)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimensions), new NumberMatcher<long>(index));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(IEnumerable<int> dimensions, long minIndex, long maxIndex)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimensions), new NumberMatcher<long>(minIndex, maxIndex));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(IEnumerable<int> dimensions, IEnumerable<long> indexes)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimensions), new NumberMatcher<long>(indexes));
            return this;
        }

        public ICompareSetup IgnoreElementsBy(IEnumerable<int> dimensions, Predicate<long> predicate)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimensions), new NumberMatcher<long>(predicate));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(Predicate<int> dimensionPredicate, long index)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimensionPredicate), new NumberMatcher<long>(index));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(Predicate<int> dimensionPredicate, long minIndex, long maxIndex)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimensionPredicate), new NumberMatcher<long>(minIndex, maxIndex));
            return this;
        }

        public ICompareSetup IgnoreElementsAt(Predicate<int> dimensionPredicate, IEnumerable<long> indexes)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimensionPredicate), new NumberMatcher<long>(indexes));
            return this;
        }

        public ICompareSetup IgnoreIndex(long index)
        {
            return IgnoreElementAt(0, index);
        }

        public ICompareSetup IgnoreIndexes(IEnumerable<long> indexes)
        {
            return IgnoreElementsAt(0, indexes);
        }

        public ICompareSetup IgnoreIndexes(long minIndex, long maxIndex)
        {
            return IgnoreElementsAt(0, minIndex, maxIndex);
        }

        public ICompareSetup IgnoreIndexesBy(Predicate<long> predicate)
        {
            return IgnoreElementsBy(0, predicate);
        }

        public ICompareSetup IgnoreElementsBy(Predicate<long[]> predicate)
        {
            _ignoredElements.AddPredicate(predicate);
            return this;
        }

        public ICompareSetup IgnoreElementsBy(Predicate<int> dimensionPredicate, Predicate<long> indexPredicate)
        {
            _ignoredElements.AddMatcher(new NumberMatcher<int>(dimensionPredicate), new NumberMatcher<long>(indexPredicate));
            return this;
        }

        public ICompareSetup IgnoreEntry(object key)
        {
            _ignoredEntries.AddValue(key);
            return this;
        }

        public ICompareSetup IgnoreEntries(IEnumerable<object> keys)
        {
            _ignoredEntries.AddValues(keys);
            return this;
        }

        public ICompareSetup IgnoreEntriesBy(Predicate<object> predicate)
        {
            _ignoredEntries.AddPredicate(predicate);
            return this;
        }

        public ICompareSetup IgnoreReadOnlyProperties(bool value)
        {
            _ignoreReadOnlyProperties = value;
            return this;
        }

        public ICompareSetup ForceEqualsComparer(bool value)
        {
            _forceEqualsComparer = value;
            return this;
        }

        public ICompareSetup ForceHashCodeComparer(bool value)
        {
            _forceHashCodeComparer = value;
            return this;
        }

        public ICompareSetup DefinePropertiesFlags(AccessFlags flags)
        {
            _propertiesFlags = flags;
            return this;
        }

        public ICompareSetup DefineFieldsFlags(AccessFlags flags)
        {
            _fieldsFlags = flags;
            return this;
        }

        public ICompareSetup CompareKeysBy(IEqualityComparer<object> comparer)
        {
            _keysComparer = comparer;
            return this;
        }

        public ICompareSetup DisableGenericComparer(string comparer)
        {
            _disabledGenericsComparers.AddValue(comparer);
            return this;
        }

        public ICompareSetup DisableGenericComparers(IEnumerable<string> comparers)
        {
            _disabledGenericsComparers.AddValues(comparers);
            return this;
        }

        public ICompareSetup DisableGenericComparersBy(string regex)
        {
            _disabledGenericsComparers.AddRegex(new Regex(regex));
            return this;
        }

        public ICompareSetup DisableGenericComparersBy(Predicate<string> predicate)
        {
            _disabledGenericsComparers.AddPredicate(predicate);
            return this;
        }
        #endregion

        #region ICompareSettings members
        public bool Setup(ICompareContext context)
        {                        
            foreach (var entry in _children)
            {
                if (entry.Left.IsMatch(context))
                {
                    _currentSettings = entry.Right;
                    return true;
                }
            }
            return false;
        }
        
        public void Restore()
        {
            _currentSettings = this;
        }

        public IResultSettings GetResult()
        {
            return _currentSettings._result;
        }

        public IObjectComparer GetComparer()
        {
            return _currentSettings._comparer;
        }

        public bool IsDisabledComparer(string name)
        {
            return _currentSettings._disabledComparers.IsMatch(name);
        }

        public bool IsDisabledGenericComparer(string name)
        {
            return _currentSettings._disabledGenericsComparers.IsMatch(name);
        }

        public bool IsIgnoredEntry(object key)
        {
            return _currentSettings._ignoredEntries.IsMatch(key);
        }

        public bool IsIgnoredElement(long index)
        {
            return _currentSettings._ignoredElements.IsMatch(index);
        }

        public bool IsIgnoredElement(long[] indexes)
        {
            return _currentSettings._ignoredElements.IsMatch(indexes);
        }

        public bool IsIgnoredField(string field)
        {
            return _currentSettings._ignoredFields.IsMatch(field);
        }

        public bool IsIgnoredProperty(string property)
        {
            return _currentSettings._ignoredProperties.IsMatch(property);
        }

        public AccessFlags PropertiesFlags
        {
            get
            {
                return _propertiesFlags;    
            }            
        }

        public AccessFlags FieldsFlags
        {
            get
            {
                return _fieldsFlags;     
            }            
        }

        public bool IsEqualsComparerForced
        {
            get { return _forceEqualsComparer; }
        }

        public bool IsHashCodeComparerForced
        {
            get { return _forceHashCodeComparer; }
        }

        public bool IsReadOnlyPropertiesIgnored
        {
            get { return _ignoreReadOnlyProperties; }
        }

        public IEqualityComparer<object> KeysComparer
        {
            get { return _keysComparer; }
        }
        #endregion

        public ICompareSetup GetSetupFor(IObjectMatcher<ICompareContext> contextMatcher)
        {
            foreach (var pair in _children)
            {
                if (pair.Left.Equals(contextMatcher))
                {
                    return pair.Right;
                }
            }
            
            var result = new CompareSetup();
            _children.Add(PairUtils.Pair(contextMatcher, result));
            return result;
        }
    }


}